<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    const bucket = new WeakMap();
    let activeEffect;
    const effectStack = []; //新增
    //枚举操作类型
    const TriggerType = {
      SET: "SET",
      ADD: "ADD"
    }
    //原始数据
    const data = {
      foo: 1
    }
    const ITEATE_KEY = Symbol();
    //设置里面的函数抽象到’追踪‘
    function track(target, key) {
      if (!activeEffect) {
        return;
      }
      let depsMap = bucket.get(target);
      if (!depsMap) {
        //用对象来做key
        bucket.set(target, (depsMap = new Map()))
      }
      let deps = depsMap.get(key);
      if (!deps) {
        depsMap.set(key, (deps = new Set()));
      }
      deps.add(activeEffect);
    }
    //设置的代码，抽象到这里
    function trigger(target, key, type) {

      const depsMap = bucket.get(target);
      if (!depsMap) {
        return;
      }
      const effects = depsMap.get(key);
      const iterateEffects = depsMap.get(ITEATE_KEY);//把和ITERATE_KEY关联的副作用取出来
      //新增
      const effectToRun = new Set();
      effects && effects.forEach(fn => {
        if (fn !== activeEffect) {
          effectToRun.add(fn);
        }
      });
      if (type === TriggerType.ADD) {
        iterateEffects && iterateEffects.forEach(fn => {
          if (fn !== activeEffect) {
            effectToRun.add(fn);
          }
        });
      }

      effectToRun.forEach(fn => {
        //副作用函数，是否存在调度器？？？？？
        console.log("被手机的effect函数是fn：", fn)
        if (fn.options && fn.options.scheduler) {
          fn.options.scheduler(fn)
        } else {
          fn();
        }
      })

    }
    const obj = new Proxy(data, {
      get(target, key) {
        track(target, key);
        return target[key]; //返回对应的值
      },
      has(target, key) {
        console.log("触发in 检测", key);
        track(target, key);
        return Reflect.has(target, key);
      },
      ownKeys(target, key) {
        console.log("触发for ... in 检测,是没有第二个参数的，所以是undefined:", key)
        track(target, ITEATE_KEY);
        return Reflect.ownKeys(target);
      },
      set(target, key, newVal, receiver) {
        const type = Object.prototype.hasOwnProperty.call(target, key) ? TriggerType.SET : TriggerType.ADD;
        //target[key] = newVal;
        const res = Reflect.set(target, key, newVal, receiver);
        //将副作用函数从桶里取出来，并执行
        trigger(target, key, type);
        return res;
      },

    })
    function computed(getter) {
      let value;
      let dirty = true;
      const effectFn = effect(getter, {
        lazy: true, scheduler() {
          dirty = true;
          trigger(obj, 'value')
        }
      })
      const obj = {
        get value() {
          if (dirty) {
            value = effectFn();
            console.log("计算")
            dirty = false;
          }
          track(obj, 'value')

          return value;
        }
      }
      return obj;
    }
    //用于注册副作用函数
    function effect(fn, options = {}) {

      const effectFn = () => {
        activeEffect = fn;
        //添加到数组最后
        effectStack.push(effectFn); //函数把自己放到数组里面
        const res = fn();//fn必须有返回值
        //
        effectStack.pop(); //移除掉数组最后一个
        activeEffect = effectStack[effectStack.length - 1];
        return res;
      }
      fn.options = options; //fn才是effect函数
      if (!options.lazy) {
        effectFn();
      }

      return effectFn;
    }
    function traverse(value, seen = new Set()) {
      //如果读取过了，就什么不做，这么核心就是读取，读取会触发收集！！
      if (typeof value !== "object" || value == null || seen.has(value)) {
        return;
      }
      seen.add(value);
      for (const k in value) {
        traverse(value[k], seen);
      }
      return value;
    }
    function watch(source, cb, options = {}) {
      let getter;
      if (typeof source === "function") {
        getter = source;
      }
      else {
        getter = () => traverse(source);
      }
      let newVal, oldVal;
      let cleanup;
      function onInvalidate(fn) {
        cleanup = fn;
      }

      const job = () => {
        newVal = effectFn();
        if (cleanup) {
          cleanup();
        }
        cb(newVal, oldVal, onInvalidate);
        oldVal = newVal;
      }
      const effectFn = effect(() => getter(), {
        scheduler: job
      })
      if (options.immediate) {
        job();
      }
      else {
        oldVal = effectFn();//非立即执行时候，第一次触发watch，oldVal不再是undefined
      }
    }
    // effect(() => {
    //   'foo' in obj;
    // });

    effect(() => {
      for (let key in obj) {
        console.log("effect for in effect函数执行", key, obj[key])
      }
    });
    obj.bar = 100;//没有触发  for in effect函数
    obj.bar = 200;//没有触发  for in effect函数

  </script>
</body>


</html>